__author__ = 'zhiweifan'
# 4.6. 深入 Python 函数定义
# 在 Python 中，你也可以定义包含若干参数的函数。 这里有三种可用的形式，也可以混合使用。
# 4.6.1. 默认参数值
# 最常用的一种形式是为一个或多个参数指定默认值。 这会创建一个可以使用比定义时允许的参数更少的参数调用的函数，例如:
# 这个例子还介绍了 in 关键字。它测定序列中是否包含某个确定的值。
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
    while True:
        ok = input(prompt)
        if ok in ('y', 'ye', 'yes'): # in 关键字。它测定序列中是否包含某个确定的值
            return True
        if ok in ('n', 'no', 'nop', 'nope'):
            return False
        retries = retries - 1
        if retries < 0:
            raise IOError('refusenik user')
        print(complaint)
# 这个函数可以通过几种不同的方式调用:
# ask_ok('Do you really want to quit?') # 只给出必要的参数:
# ask_ok('OK to overwrite the file?', 2) # 给出一个可选的参数:
# ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!') # 或者给出所有的参数:

#
# 默认值在函数 定义 作用域被解析，如下所示
i = 5
def f(arg=i):
    print(arg)
i = 6
f() #将会输出 5.

# 重要警告: 默认值只被赋值一次。这使得当默认值是可变对象时会有所不同，比如列表、字典或者大多数类的实例。例如，下面的函数在后续调用过程中会累积（前面）传给它的参数:
def f(a, L=[]):
    L.append(a)
    return L
print(f(1))
print(f(2))
print(f(3))
# 这将输出
# [1]
# [1, 2]
# [1, 2, 3]

# 如果你不想让默认值在后续调用中累积，你可以像下面一样定义函数:
def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
print(f(1))
print(f(2))
print(f(3))
# 4.6.2. 关键字参数
# 函数可以通过 关键字参数 的形式来调用，形如 keyword = value 。例如，以下的函数:
def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.")
    print("-- Lovely plumage, the", type)
    print("-- It's", state, "!")
# 接受一个必选参数 (voltage) 以及三个可选参数 (state, action, 和 type). 可以用以下的任一方法调用:
parrot(1000)                                          # 1 positional argument
parrot(voltage=1000)                                  # 1 keyword argument
parrot(voltage=1000000, action='VOOOOOM')             # 2 keyword arguments
parrot(action='VOOOOOM', voltage=1000000)             # 2 keyword arguments
parrot('a million', 'bereft of life', 'jump')         # 3 positional arguments
parrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword
# 不过以下几种调用是无效的:
# parrot()                     # required argument missing
# parrot(voltage=5.0, 'dead')  # non-keyword argument after a keyword argument
# parrot(110, voltage=220)     # duplicate value for the same argument
# parrot(actor='John Cleese')  # unknown keyword argument
# 通常，参数列表必须（先书写）位置参数然后才是关键字参数，这里关键字必须来自于形参名字。 形参是否有一个默认值并不重要。 任何参数都不能被多次赋值——在同一个调用中，与位置参数相同的形参名字不能用作关键字。 这里有一个违反此限制而出错的例子:
# >>> def function(a):
# ...     pass
# >>> function(0, a=0)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in ?
# TypeError: function() got multiple values for keyword argument 'a'
# 引入一个形如 **name 的参数时，它接收一个字典（参见 typesmapping ） ，该字典包含了所有未出现在形式参数列表中的关键字参数。这里可能还会组合使用一个形如 *name （下一小节詳細介绍） 的形式参数，
# 它接收一个元组（下一节中会详细介绍），包含了所有没有出现在形式参数列表中的参数值。（ *name 必须在 **name 之前出现） 例如，我们这样定义一个函数:
def cheeseshop(kind, *arguments, **keywords):
    print("-- Do you have any", kind, "?")
    print("-- I'm sorry, we're all out of", kind)
    for arg in arguments:
        print(arg)
    print("-" * 40)
    keys = sorted(keywords.keys())
    for kw in keys:
        print(kw, ":", keywords[kw])
# 它可以像这样调用:
cheeseshop("Limburger", "It's very runny, sir.",
           "It's really very, VERY runny, sir.",
           shopkeeper="Michael Palin",
           client="John Cleese",
           sketch="Cheese Shop Sketch")
# 当然它会按如下内容打印:
# -- Do you have any Limburger ?
# -- I'm sorry, we're all out of Limburger
# It's very runny, sir.
# It's really very, VERY runny, sir.
# ----------------------------------------
# client : John Cleese
# shopkeeper : Michael Palin
# sketch : Cheese Shop Sketch
# 注意在打印 关系字 参数字典的内容前先调用 sort() 方法。否则的话，打印参数时的顺序是未定义的。

# 4.6.3. 可变参数列表
# 最后，一个最不常用的选择是可以让函数调用可变个数的参数。这些参数被包装进一个元组（参见 元组和序列 ）。在这些可变个数的参数之前，可以有零到多个普通的参数。
def write_multiple_items(file, separator, *args):
    file.write(separator.join(args))
# 通常，这些 可变 参数是参数列表中的最后一个， 因为它们将把所有的剩余输入参数传递给函数。任何出现在 *args 后的参数是关键字参数，这意味着，他们只能被用作关键字，而不是位置参数。:
def concat(*args, sep="/"):
    return sep.join(args)
print(concat("earth", "mars", "venus")) # 'earth/mars/venus'
concat("earth", "mars", "venus", sep=".") # 'earth.mars.venus'

# 4.6.4. 参数列表的分拆
# 另有一种相反的情况: 当你要传递的参数已经是一个列表，但要调用的函数却接受分开一个个的参数值. 这时候你要把已有的列表拆开来. 例如内建函数 range() 需要要独立的 start , stop 参数. 你可以在调用函数时加一个 * 操作符来自动把参数列表拆开:
list(range(3, 6))            # normal call with separate arguments
# [3, 4, 5]
args = [3, 6]
list(range(*args))            # call with arguments unpacked from a list
# [3, 4, 5]
# 以同样的方式，可以使用 ** 操作符分拆关键字参数为字典:
def parrot(voltage, state='a stiff', action='voom'):
    print("-- This parrot wouldn't", action, end=' ')
    print("if you put", voltage, "volts through it.", end=' ')
    print("E's", state, "!")
d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
parrot(**d)
# -- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !
# 4.6.5. Lambda 形式
# 出于实际需要，有几种通常在函数式编程语言例如 Lisp 中出现的功能加入到了 Python 。通过 lambda 关键字，可以创建短小的匿名函数。这里有一个函数返回它的两个参数的和： lambda a, b: a+b 。
# Lambda 形式可以用于任何需要的函数对象。出于语法限制，它们只能有一个单独的表达式。语义上讲，它们只是普通函数定义中的一个语法技巧。类似于嵌套函数定义，lambda 形式可以从外部作用域引用变量:
def make_incrementor(n):
    return lambda x: x + n
f = make_incrementor(42)
print(f(0)) # 42
print(f(1)) # 43
# 4.6.6. 文档字符串
# 这里介绍的文档字符串的概念和格式。
# 第一行应该是关于对象用途的简介。简短起见，不用明确的陈述对象名或类型，因为它们可以从别的途径了解到（除非这个名字碰巧就是描述这个函数操作的动词）。这一行应该以大写字母开头，以句号结尾。
# 如果文档字符串有多行，第二行应该空出来，与接下来的详细描述明确分隔。接下来的文档应该有一或多段描述对象的调用约定、边界效应等。
# Python 的解释器不会从多行的文档字符串中去除缩进，所以必要的时候应当自己清除缩进。这符合通常的习惯。第一行之后的第一个非空行决定了整个文档的缩进格式。
# （我们不用第一行是因为它通常紧靠着起始的引号，缩进格式显示的不清楚。）留白“相当于”是字符串的起始缩进。每一行都不应该有缩进，如果有缩进的话，所有的留白都应该清除掉。留白的长度应当等于扩展制表符的宽度（通常是8个空格）。
#
# 以下是一个多行文档字符串的示例:
#
# >>> def my_function():
# ...     """Do nothing, but document it.
# ...
# ...     No, really, it doesn't do anything.
# ...     """
# ...     pass
# ...
# >>> print(my_function.__doc__)
# Do nothing, but document it.
#
#     No, really, it doesn't do anything.
# 4.7. 插曲：编码风格
# 此时你已经可以写一此更长更复杂的 Python 程序，是时候讨论一下 编码风格 了。大多数语言可以写（或者更明白的说， 格式化 ）作几种不同的风格。有些比其它的更好读。让你的代码对别人更易读是个好想法，养成良好的编码风格对此很有帮助。
#
# 对于 Python， PEP 8 引入了大多数项目遵循的风格指导。它给出了一个高度可读，视觉友好的编码风格。每个 Python 开发者都应该读一下，大多数要点都会对你有帮助：
#
# 使用 4 空格缩进，而非 TAB。
#
# 在小缩进（可以嵌套更深）和大缩进（更易读）之间，4空格是一个很好的折中。TAB 引发了一些混乱，最好弃用。
#
# 折行以确保其不会超过 79 个字符。
#
# 这有助于小显示器用户阅读，也可以让大显示器能并排显示几个代码文件。
#
# 使用空行分隔函数和类，以及函数中的大块代码。
#
# 可能的话，注释独占一行
#
# 使用文档字符串
#
# 把空格放到操作符两边，以及逗号后面，但是括号里侧不加空格： a = f(1, 2) + g(3, 4) 。
#
# 统一函数和类命名。
#
# 推荐类名用 驼峰命名， 函数和方法名用 小写_和_下划线。总是用 self 作为方法的第一个参数（关于类和方法的知识详见 初识类 ）。
#
# 不要使用花哨的编码，如果你的代码的目的是要在国际化 环境。 Python 的默认情况下，UTF-8，甚至普通的 ASCII 总是工作的最好。
#
# 同样，也不要使用非 ASCII 字符的标识符，除非是不同语种的会阅读或者维护代码。
#
# Footnotes
#
# [1]	实际上， 引用对象 调用描述的更为准确。如果传入一个可变对像，调用者会看到调用操作带来的任何变化（如子项插入到列表中）。
